"""
Copyright 2007, Thomas Dejanovic.

This is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.

This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this software; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
"""

id = "$Id: hatch_mux.py 667 2010-07-01 00:12:17Z jayshurtz $"
# $URL: http://hatch.googlecode.com/svn/trunk/hatch/hatch_nodes/obsolete/hatch_mux.py $
# $Author: jayshurtz $
version = " version ".join(id.split()[1:3])


#import re
#import math
#import hatch_target_bus
#from hatch_node_physical import *
from hatch_struct import *

class Mux(Struct):
    """ A mux, such as an ABP Slave Mux.
        Muxes at least one register block or other slave mux.
    """
    def __init__(self, name, *childNodes, **properties):
        """ """
        Struct.__init__(self, name, *childNodes, **properties)

        #args = {'type':'apb slave mux'}
        #if not properties.has_key('base_address'):
        #    args['base_address'] = 0L
        #args.update(properties)
        #HatchBlock.__init__(self, name, *childNodes, **args)

        ## build a target bus.
        #self.set_property('target', 'apb slave')
        #self.set_property('target_bus', hatch_target_bus.Target_Bus(self))  # XXX Done with IO ports.

#    def InputPort (self, name, fileName, **keyargs):
#        """ Allow creation of a field in the register after the fact. """
#        self.add_content(InputPort(name, fileName, **keyargs))

    # These functions have all been moved into the hatch address mapper visitor class.
    if False:
        def elaborate (self):
            """ Run any functions needed to flesh out data structures as a
                prelude to generating anything.
            """
            for c in self.contentList:
                c.elaborate()
            # now find the address blocks.
            fixedBlockList = []
            movableBlockList = []
            for c in self.contentList:
                addrRange = c.get_address_range()
                baseAddr = c.get_base_address()
                decodeBits = int(math.ceil(math.log(addrRange) / math.log(2)))
                addrRange = (2L ** decodeBits)
                mask = addrRange - 1L
                if (baseAddr & mask) != 0:
                    raise """*** ERROR - the base address 0x%x for %s is not alligned correctly for the
                    address mask generated 0x%x."""%(baseAddr, c.name, mask)
                if baseAddr == 0:
                    movableBlockList.append([baseAddr, addrRange, c])
                else :
                    fixedBlockList.append([baseAddr, addrRange, c]) 
            # now go through the movable blocks and sort them by size
            _sort_block_list_by_size_(movableBlockList)
            # OK, now pack the movable blocks into the free spaces.  Use
            # the smallest spaces first and then pack stuff above any
            # predefined blocks.
            # simple pack.  There is some polishing that could be done
            # here, but simple gets it done in finite time :-)
            for b, r, c in movableBlockList:
                # find any free space between fixed blocks.
                freeBlockList = _build_free_block_list_(fixedBlockList)
                # get a free block big enaugh to fit the current movable block.
                baseAddr = _get_base_addre_of_space_(freeBlockList, r)
                fixedBlockList.append([baseAddr, r, c])
            # go through and update the base address in all the fixed blocks.
            for b, r, c in fixedBlockList:
                c.set_base_address(b)
                print "updated base address of", c.name, "to 0x%x"%(b)
            for b, r, c in fixedBlockList:
                x = c.get_base_address()
                print "double checkeing base address of", c.name, "is 0x%x, should be 0x%x"%(x, b)
            # store the range and base for this mux for handy access by
            # other levels in the tree.
            _sort_block_list_by_base_(fixedBlockList)
            baseAddr = fixedBlockList[-1][0]
            # range is the highest block + it's range - our base.
            addrRange =fixedBlockList[0][0] + fixedBlockList[0][1] - baseAddr
            self.set_property('base_address', baseAddr)
            self.set_property('address_range', addrRange)
            self.property('target_bus').elaborate()
        def get_base_address (self):
            """ Return the base address for this register block."""
            if self.has_property('base_address'):
                return self.property('base_address')
            else:
                return 0L
        def get_address_range (self, addrBase=0):
            """ Return the number of locations required for this apb slave mux."""
            if not self.has_property('address_range'):
                self.elaborate()
            return self.property('address_range')


# This class is not needed.
if False:
    class InputPort (HatchNodePhysical):
        """ A connection into this apb slave mux.
            It is important to note that the same module may be included
            multiple times so we cannot push information like base address
            into instance of these modules. we need to keep instance specific
            information at the InputPort level.
        """
        def __init__(self, name, fileName, **keyargs):
            args = {'type':'port'}
            args.update(keyargs)
            HatchLeaf.__init__(self, name, **args)

            m = re.match("^(?P<base>.+)\.py", fileName)
            if not m:
                raise "*** ERROR - could not determine base name for %s"%(name)
            
            # now import the hatchling and store it for future reference.
            exec("import %s as hatchling"%(m.group('base')))
            self.hatchling = hatchling.hatchBlock
            del hatchling

            # check that the name and the prefix have some comopnality.
            if not re.match("^%s\d*_$"%(self.hatchling.property('prefix')[:-1]), self.name):
                print "prefix : ", self.hatchling.property('prefix')
                raise """*** ERROR - port names should match the hatchling prefix followed
    by an optional index to support arrays or multiply instantiated
    hatchlings.  For example modules with prefix foo_ should only have
    names of the form foo[0-9]*_. port name %s, hatchling %s."""%(self.name, fileName)

    #    def elaborate (self):
    #        """run any functions needed to flesh out data structures as a
    #        prelude to generating anything."""
    #        self.hatchling.elaborate()

        def __str__(self, indent=""):
            return "%sAPB Slave MUX input port - %s\n"%(indent, self.name)

        # These functions have all been moved into the hatch address mapper visitor class.
        if False:
            def get_base_address (self):
                """return the base address for this register block."""
                if not self.has_property('base_address'):
                    self.set_property('base_address', self.hatchling.get_base_address())
                return self.property('base_address')

            def set_base_address (self, baseAddr):
                """set the base address for this register block."""
                self.set_property('base_address', baseAddr)

            def get_address_range (self):
                """return the number of locations required for this block."""
                return self.hatchling.get_address_range()


# These functions have all been moved into the hatch address mapper visitor class.
if False:
    def _sort_block_list_by_size_(l):
        """ Go through the block list and sort them by size.
            Classic bubble sort.
        """
        for a in range(len(l) - 1):
            for b in range(len(l) - a - 1):
                s1 = l[b][1]    # [1]
                s2 = l[b+1][1]
                if s2 > s1:
                    t = l[b+1]
                    l[b+1] = l[b]
                    l[b] = t
        return l

    def _sort_block_list_by_base_(l):
        """ Go through the blocks and sort them by base.
            Classic bubble sort.
        """
        for a in range(len(l) - 1):
            for b in range(len(l) - a - 1):
                b1 = l[b][0]    # [0]
                b2 = l[b+1][0]
                if b2 > b1:
                    t = l[b+1]
                    l[b+1] = l[b]
                    l[b] = t
        return l

    def _build_free_block_list_(fixedBlockList):
        """ Build a list of free blocks from the list of blocks already
            alocated and locked down.
        """
        # copy the fixed block list.
        fbl = []
        for b in fixedBlockList:
            fbl.append(b)

        freeBlockList = []
        
        # if there is already stuff in the list, don't add the bottom of
        # memory, we only wnat to grow up from any predefined modules.
        if len(fbl) == 0:
            global BOTTOM_OF_MEMORY
            fbl.append([BOTTOM_OF_MEMORY,0, None])

        # add a fixed block at the top of memory
        global TOP_OF_MEMORY
        fbl.append([TOP_OF_MEMORY,0, None])
        # now go through the fixed blocks and sort them by base address
        _sort_block_list_by_base_(fbl)

        # now check that they do not overlap.
        for a in range(len(fbl) - 1):
            # base 1 is higer in memeory than base 2.
            b1 = fbl[a][0]
            r1 = fbl[a][1]
            b2 = fbl[a+1][0]
            r2 = fbl[a+1][1]
            if b1 > b2 and b1 < (b2 + r2) :
                e = """*** ERROR - base address 0x%x of %s inside the address range of
            %s which goes from 0x%x to 0x%x."""%(b1, fbl[a][2].hatchling.name,
                                                 fbl[a+1][2].hatchling.name, b2, b2+r2)
                raise e

        # now find any free space between blocks.
        for a in range(len(fbl) - 1):
            # base 1 is higer in memeory than base 2.
            b1 = fbl[a][0]
            r1 = fbl[a][1]
            b2 = fbl[a+1][0]
            r2 = fbl[a+1][1]
            # how big is the space between these two blocks?
            freeBase = b2 + r2
            freeRange = b1 - freeBase
            freeBlockList.append([freeBase, freeRange])
            # print "free space between 0x%x and 0x%x"%(freeBase, freeBase + freeRange)
        # now go through the free blocks and sort them by size
        _sort_block_list_by_size_(freeBlockList)
        # want the small free blocks at the top.
        freeBlockList.reverse()

        return freeBlockList

    def _get_base_addre_of_space_(freeBlockList, size):
        """ Find a space in the list of free block that is big enaugh to
            fit the block we have and return the base address of the location
            to use.
        """
        i = 0
        r = None
        decodeBits = int(math.ceil(math.log(size) / math.log(2)))
        while i < len(freeBlockList):
            b = freeBlockList[i][0] # base
            s = freeBlockList[i][1] # size
            if s >= size:
                # make a "nice' decode.
                addrRange = (2L ** decodeBits)
                mask = addrRange - 1L
                baseAddr = b
                if (baseAddr & mask) != 0:
                    baseAddr = (baseAddr & mask) + addrRange
                # check that it now fits.
                if (baseAddr + addrRange) <= (b + s):
                    # it fits!
                    r = baseAddr
                    break
                # it did not fit, so keep looking.
            i += 1
        if r == None:
            raise "*** Could not find free space for the movable block."
        return r


# Main for testing.
if __name__ == "__main__":

    # Test class
    s = Mux(name='timers', packed=False, target='small apb slave', input_ports="""tick""", prefix="test_a_", header=""" bla """)
    print s.info()


